
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include "esp_log.h"
#include "esp_system.h"
#include "esp_err.h"
#include "esp_check.h"

#include "i2c_bus.h"

#include "xl9535.h"

// #define I2C_XL9535_MAX_TRANS_UNIT (8)

static const char *const TAG = "xl9535";

#ifdef XL9535_USE_DEFAULT_HANDLE
i2c_xl9535_handle_t i2c_dev_xl9535[XL9535_MAX_I2C_DEVICE] = {NULL, NULL};      /*!< I2C device handle */
#endif

static esp_err_t i2c_xl9535_read(i2c_xl9535_handle_t xl9535_handle, uint8_t address, uint8_t *data, uint32_t size);

esp_err_t xl9535_init(void *bus_handle, const i2c_xl9535_config_t *xl9535_config, i2c_xl9535_handle_t *xl9535_handle)
{
    esp_err_t ret = ESP_OK;
    i2c_xl9535_t *out_handle;
    out_handle = (i2c_xl9535_t *)calloc(1, sizeof(i2c_xl9535_t));
    ESP_GOTO_ON_FALSE(out_handle, ESP_ERR_NO_MEM, err, TAG, "no memory for i2c xl9535 device");

    if (out_handle->i2c_dev == NULL) {
        out_handle->i2c_dev = i2c_bus_device_create( bus_handle, xl9535_config->dev_addr, xl9535_config->clk_speed);
    }
    if(out_handle->i2c_dev == NULL) {
        ret = ESP_ERR_INVALID_ARG;
        goto err;
    }
    // out_handle->buffer = (uint8_t*)calloc(1, xl9535_config->addr_wordlen + I2C_XL9535_MAX_TRANS_UNIT);
    // ESP_GOTO_ON_FALSE(out_handle->buffer, ESP_ERR_NO_MEM, err, TAG, "no memory for i2c xl9535 device buffer");

    // out_handle->addr_wordlen = xl9535_config->addr_wordlen;
    // out_handle->write_time_ms = xl9535_config->write_time_ms;
    *xl9535_handle = out_handle;

    // Check to see if the device can read from the register
    uint8_t port = 0;
    if (i2c_xl9535_read(out_handle, XL9535_INPUT_PORT_0_REGISTER, &port, 1) != ESP_OK) {
        ret = ESP_FAIL;
        goto err;
    }

    return ESP_OK;

err:
    if (out_handle && out_handle->i2c_dev) {
        i2c_bus_device_delete(&out_handle->i2c_dev);
    }
    free(out_handle);
    return ret;
}

static esp_err_t i2c_xl9535_write(i2c_xl9535_handle_t xl9535_handle, uint8_t address, const uint8_t *data, uint32_t size)
{
    ESP_RETURN_ON_FALSE(xl9535_handle, ESP_ERR_NO_MEM, TAG, "no mem for buffer");

    return i2c_bus_write_bytes(xl9535_handle->i2c_dev, address, size, data);
}

static esp_err_t i2c_xl9535_read(i2c_xl9535_handle_t xl9535_handle, uint8_t address, uint8_t *data, uint32_t size)
{
    ESP_RETURN_ON_FALSE(xl9535_handle, ESP_ERR_NO_MEM, TAG, "no mem for buffer");

    return i2c_bus_read_bytes(xl9535_handle->i2c_dev, address, size, data);
}

int xl9535_digital_read(i2c_xl9535_handle_t dev_handle, uint8_t pin) {
    uint8_t register_data;
    if (pin > 7) {
        if (i2c_xl9535_read(dev_handle, XL9535_INPUT_PORT_1_REGISTER, &register_data, 1) != ESP_OK) {
            return -1;
        }
        return (register_data >> (pin - 8)) & 1;
    } else {
        if (i2c_xl9535_read(dev_handle, XL9535_INPUT_PORT_0_REGISTER, &register_data, 1) != ESP_OK) {
            return -1;
        }
        return (register_data >> pin) & 1;
    }
}

void xl9535_digital_write(i2c_xl9535_handle_t dev_handle, uint8_t pin, bool value) {
    uint8_t register_data, port;

    if (pin > 7) {
        if (i2c_xl9535_read(dev_handle, XL9535_OUTPUT_PORT_1_REGISTER, &register_data, 1) != ESP_OK) {
            return;
        }
        register_data = register_data & (~(1 << (pin - 8)));
        port = register_data | (value << (pin - 8));

        if (i2c_xl9535_write(dev_handle, XL9535_OUTPUT_PORT_1_REGISTER, &port, 1) != ESP_OK) {
            return;
        }
    } else {
        if (i2c_xl9535_read(dev_handle, XL9535_OUTPUT_PORT_0_REGISTER, &register_data, 1) != ESP_OK) {
            return;
        }
        register_data = register_data & (~(1 << pin));
        port = register_data | (value << pin);

        if (i2c_xl9535_write(dev_handle, XL9535_OUTPUT_PORT_0_REGISTER, &port, 1) != ESP_OK) {
            return;
        }
    }
}

void xl9535_pin_mode(i2c_xl9535_handle_t dev_handle, uint8_t pin, uint8_t mode) {
    uint8_t port = 0;

    if (pin > 7) {
        i2c_xl9535_read(dev_handle, XL9535_CONFIG_PORT_1_REGISTER, &port, 1);

        if (mode == XL9535_GPIO_FLAG_INPUT) {
            port = port | (1 << (pin - 8));
        } else if (mode == XL9535_GPIO_FLAG_OUTPUT) {
            port = port & (~(1 << (pin - 8)));
        }

        i2c_xl9535_write(dev_handle, XL9535_CONFIG_PORT_1_REGISTER, &port, 1);
    } else {
        i2c_xl9535_read(dev_handle, XL9535_CONFIG_PORT_0_REGISTER, &port, 1);

        if (mode == XL9535_GPIO_FLAG_INPUT) {
            port = port | (1 << pin);
        } else if (mode == XL9535_GPIO_FLAG_OUTPUT) {
            port = port & (~(1 << pin));
        }

        i2c_xl9535_write(dev_handle, XL9535_CONFIG_PORT_0_REGISTER, &port, 1);
    }
}

void xl9535_gpio_pin_setup(xl9535_gpio_t *gpio_pin) {
    xl9535_pin_mode(gpio_pin->parent, gpio_pin->pin, gpio_pin->flags);
}

char* xl9535_gpio_pin_dump_summary(xl9535_gpio_t *gpio_pin) {
    static char summary[50];
    snprintf(summary, 50, "%"PRIx8" via XL9535", gpio_pin->pin);
    return summary;
}

int xl9535_gpio_digital_read(xl9535_gpio_t *gpio_pin) {
    return xl9535_digital_read(gpio_pin->parent, gpio_pin->pin) != gpio_pin->inverted;
}

void xl9535_gpio_digital_write(xl9535_gpio_t *gpio_pin, bool value) {
    xl9535_digital_write(gpio_pin->parent, gpio_pin->pin, value != gpio_pin->inverted);
}
